/**
 * Copyright (c) 2009-2011, chunquedong(YangJiandong)
 * 
 * This file is part of ChunMap project
 * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE(Version >=3)
 * 
 * History:
 *     2010-05-05  Jed Young  Creation
 */
package chunmap.model.operate.overlay;

import java.util.ArrayList;
import java.util.List;

import chunmap.model.coord.CoordSeqEditor;
import chunmap.model.elem.PointLineBag;
import chunmap.model.geom.GeometryException;
import chunmap.model.geom.LineString;
import chunmap.model.geom.Ring;
import chunmap.model.geom.Polygon;
import chunmap.model.relate.ComputeIm;
import chunmap.model.relate.ComputeImFactory;
import chunmap.model.relate.IntersectionMatrix;

/**
 * @author chunquedong
 * 
 */
public class FragmentCollection {

	private final List<Fragment> fragments1 = new ArrayList<Fragment>();
	private final List<Fragment> fragments2 = new ArrayList<Fragment>();

	public FragmentCollection(Polygon r1, Polygon r2) {
		if (!r1.isValid() || !r2.isValid()) {
			throw new GeometryException();
		}

		PointLineBag bag = getIntersection(r1, r2);

		addPolygon(fragments1, r1, r2, bag);
		addPolygon(fragments2, r2, r1, bag);
	}

	private void addPolygon(List<Fragment> fragment1, Polygon pg,
			Polygon other, PointLineBag bag) {
		
		fillList(fragment1, bag.breakLine(pg.getShell()), other);

		List<Ring> rs = pg.getHoles();
		for (Ring r : rs) {
			fillList(fragment1, bag.breakLine(r), other);
		}
		return;
	}

	private PointLineBag getIntersection(Polygon pg1, Polygon pg2) {
		PointLineBag tbag = subGetIntersection(pg1.getShell(), pg2);
		List<Ring> rs = pg1.getHoles();
		for (Ring r : rs) {
			tbag.add(subGetIntersection(r, pg2));
		}
		return tbag;
	}

	private PointLineBag subGetIntersection(Ring r1, Polygon pg2) {
		PointLineBag tbag = r1.intersection(pg2.getShell());
		List<Ring> rs = pg2.getHoles();
		for (Ring r : rs) {
			tbag.add(r1.intersection(r));
		}
		return tbag;
	}

	private void fillList(List<Fragment> fragment1,
			List<LineString> lineStrings, Polygon other) {

		for (LineString ls : lineStrings) {
			Fragment fra = new Fragment();
			fra.setLineString(ls);

			ComputeIm cim = ComputeImFactory.getInstance().getImComputer(ls,
					other);
			IntersectionMatrix im = cim.getIM();
			boolean isWithin = isWithin(im);
			boolean isBorder = isOnBorder(im);

			if (isBorder) {
				fra.setContainIn(Fragment.Border);
			} else if (isWithin) {
				fra.setContainIn(Fragment.Inner);
			} else {
				fra.setContainIn(Fragment.Outer);
			}
			fragment1.add(fra);
		}
	}

	public boolean isOnBorder(IntersectionMatrix im) {
		boolean b1 = (im.get(IntersectionMatrix.Inner,
				IntersectionMatrix.Border) != IntersectionMatrix.EmptyDim);
		boolean b2 = im.get(IntersectionMatrix.Inner, IntersectionMatrix.Inner) == IntersectionMatrix.EmptyDim;
		boolean b3 = im.get(IntersectionMatrix.Inner, IntersectionMatrix.Outer) == IntersectionMatrix.EmptyDim;

		if (b1 && b2 && b3)
			return true;
		return false;
	}

	public boolean isWithin(IntersectionMatrix im) {
		boolean b1 = (im
				.get(IntersectionMatrix.Inner, IntersectionMatrix.Outer) == IntersectionMatrix.EmptyDim);

		if (b1)
			return true;
		return false;
	}

	public List<Polygon> getRing(int a, int b) {
		List<Fragment> fragments = new ArrayList<Fragment>();

		fillFragmentList(fragments, fragments1, a);
		fillFragmentList(fragments, fragments2, b);
		fillFragmentList(fragments, fragments1, Fragment.Border);

		List<Polygon> result = new ArrayList<Polygon>();
		Ring r = join(fragments);
		while (r != null) {
			assert r.isValid() : "the ring is not simple";
			addRing(r, result);
			r = join(fragments);
		}

		clearFlag(fragments);
		return result;
	}

	private void addRing(Ring r, List<Polygon> pgs) {
		List<Polygon> newPgs = new ArrayList<Polygon>();
		List<Polygon> oldPgs = new ArrayList<Polygon>();
		for (int i = 0, n = pgs.size(); i < n; i++) {
			Polygon pg = pgs.get(i);
			if (r.containLineStringIn(pg.getShell())) {
				List<Ring> rr = new ArrayList<Ring>();
				rr.add(pg.getShell());
				newPgs.add(new Polygon(r, rr));
				oldPgs.add(pg);
			} else if (pg.getShell().containLineStringIn(r)) {
				List<Ring> rr = new ArrayList<Ring>();
				rr.add(r);
				newPgs.add(new Polygon(pg.getShell(), rr));
				oldPgs.add(pg);
			}
		}

		if (newPgs.size() == 0) {
			pgs.add(new Polygon(r));
		} else {
			for (Polygon pg : oldPgs) {
				pgs.remove(pg);
			}
			pgs.addAll(newPgs);
		}
	}

	private void fillFragmentList(List<Fragment> result, List<Fragment> old,
			int a) {
		for (Fragment fra : old) {
			if (fra.isContainIn() == a) {
				result.add(fra);
			}
		}
	}

	private Ring join(List<Fragment> fragment) {
		CoordSeqEditor line = new CoordSeqEditor();
		int n = fragment.size();
		boolean none = true;

		while (true) {
			for (int i = 0; i < n; i++) {
				if (!fragment.get(i).isFlag()) {
					LineString ls = fragment.get(i).getLineString();
					if (line.canJoin(ls.getPoints())) {
						line.join(ls.getPoints());
						fragment.get(i).setFlag(true);
						none = false;
					}
				}
			}
			if (none) {
				break;
			}
			none = true;
		}
		if (line.isEmpty()) {
			return null;
		}
		assert line.isClose() : "the ring is not close";

		line.deleteOverPoint();
		line.close();

		return new Ring(line.toCoordinateSeq());
	}

	private void clearFlag(List<Fragment> fragment) {
		for (Fragment fra : fragment) {
			fra.setFlag(false);
		}
	}

	@Override
	public String toString() {
		return "FragmentCollection [fragments1size=" + fragments1.size()
				+ ", fragments2size=" + fragments2.size() + "]";
	}
}